#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>

#define CITY_NAME_LEN 30
#define SEND_BUF_LEN 33
#define RECV_BUF_LEN 128

//Server Socket Address:IP+PORT
const in_port_t SERVER_PORT=4321;
const char* SERVER_IP="47.105.85.28";

//Client Msg Index
const uint16_t CITY_CHOICE_MSG_INDEX=0x0100;
const uint16_t SINGLE_CITY_QUERY=0x0201;
const uint16_t MULTI_CITY_QUERY=0x0202;

//Server Reply Index
const uint16_t CITY_EXIST_REPLY_INDEX=0X0100;
const uint16_t CITY_NOT_EXIST_REPLY_INDEX=0X0200;
const uint16_t SINGLE_CITY_REPLY=0x0341;
const uint16_t MULTI_CITY_REPLY=0x0342;
const uint16_t NO_INFO_REPLY=0x0441;

enum query_type{
    SINGLE_QUERY,
    MULTI_QUERY,
    CITY_CHOICE
};

//33bytes
struct client_msg{
    uint16_t msg_index; //2 bytes
    char city_name[CITY_NAME_LEN]; //30 bytes
    u_char day_index; //1 byte
};

struct weather_info{
    u_char weather_index; //1 byte
    u_char temp; //1 byte
};

struct server_reply{
    uint16_t reply_index; //2 bytes
    char city_name[CITY_NAME_LEN]; //30 bytes
    uint16_t year; //2 bytes
    u_char mouth;  //1 byte
    u_char day;    //1 byte
    u_char tmme_interval; //1 byte
    struct weather_info info[3]; //2 bytes ～ 6 bytes
};

//get server's socket address
struct sockaddr_in get_serveraddr(){
    struct sockaddr_in serveraddr;
    memset(&serveraddr,0,sizeof(serveraddr));

    serveraddr.sin_family=AF_INET;
    serveraddr.sin_addr.s_addr=inet_addr(SERVER_IP);
    serveraddr.sin_port=htons(SERVER_PORT);

    return serveraddr;
}

//open socket and connect server
int init(){
    int socketfd=socket(AF_INET,SOCK_STREAM,0);

    if(socketfd<=0){
        printf("err! socket open failed!\n");
        exit(-1);
    }

    struct sockaddr_in serveraddr=get_serveraddr();

    if(connect(socketfd,&serveraddr,sizeof(serveraddr))<0){
        printf("err! server connect failed!\n");
        exit(-1);
    }

    return socketfd;
}

void cls(){
    printf("\033c");
}

//query server for weather information
struct server_reply query(int socketfd,char* city_name,int day_index,enum query_type type){
    struct client_msg msg;
    memset(&msg,0,sizeof(msg));
    //set client msg index
    if(type==SINGLE_QUERY){
        msg.msg_index=htons(SINGLE_CITY_QUERY);
    }else if(type==MULTI_QUERY){
        msg.msg_index=htons(MULTI_CITY_QUERY);
    }else if(type==CITY_CHOICE){
        msg.msg_index=htons(CITY_CHOICE_MSG_INDEX);
    }else{
        printf("err! unknoewn query type!");
        exit(-1);
    }

    strncpy(msg.city_name,city_name,strlen(city_name));
    msg.day_index=day_index;

    //send msg
    char send_buf[SEND_BUF_LEN];
    memcpy(send_buf,&msg,sizeof(msg));
    if((send(socketfd,send_buf,SEND_BUF_LEN,0)<=0)){
        printf("err! send msg failed!\n");
        exit(-1);
    };
    
    //receive reply
    char recv_buf[RECV_BUF_LEN];
    if(recv(socketfd,recv_buf, RECV_BUF_LEN,0) <= 0){
        printf("err! receive reply failed!\n");
        exit(-1);
    }

    struct server_reply reply;
    memset(&reply,0,sizeof(reply));
    memcpy((char*)&reply,recv_buf,sizeof(reply));
    
    //ntoh
    reply.reply_index=ntohs(reply.reply_index);
    reply.year=ntohs(reply.year);

    return reply;
}

struct server_reply single_query(int socketfd,char* city_name,int day_index){
    return query(socketfd,city_name,day_index,SINGLE_QUERY);
}

struct server_reply multi_query(int socketfd,char* city_name){
    return query(socketfd,city_name,3,MULTI_QUERY);
}

struct server_reply city_exists_query(int socketfd,char* city_name){
    return query(socketfd,city_name,0,CITY_CHOICE);
}

//show query information
void query_view(){
    printf("Please enter the given number to query\n\
1.today\n\
2.three days from today\n\
3.custom day by yourself\n\
(r)back,(c)cls,(#)exit\n\
===================================================\n");
}

char* get_weather_form_index(u_char weather_index){
    if(weather_index==0)
        return " overcast";
    else if(weather_index==1)
        return "sunny";
    else if(weather_index==2)
        return "cloudy";
    else if(weather_index==3)
        return "rain";
    else if(weather_index==4)
        return "fog";
    else if(weather_index==5)
        return "rainstorm";
    else if(weather_index==6)
        return "thunderstorm";
    else if(weather_index==7)
        return "breeze";
    else if(weather_index==8)
        return "sandstorm";
    else 
        return "unknown";
}

void input_error_message(){
    printf("input error!\n");
}

void no_info_message(char* city_name){
    printf("Sorry, no given day's weather information for city %s!\n",city_name);
}

//input day number
u_char get_day_number(){
    u_char day_number=0;
    while(1){
        u_char input[10];
        memset(input,0,sizeof(input));
        printf("Please enter the day number(below 10, e.g. 1 means today):");
        scanf("%s",input);
        if(strlen(input)==1 && (day_number=input[0]-'0')<10 && day_number>0)
            return day_number;
        else
            input_error_message();
    }
}

//show weather information
void weather_info_view(struct weather_info info,u_char day_number){
    char* weather_str=get_weather_form_index(info.weather_index);
    printf("The %dth day's weather is:%s,",day_number,weather_str);
    printf("Temp:%d\n",info.temp);
}

//show server reply
void reply_view(struct server_reply reply,int is_multi){
    printf("City:%s,Today is %d/%d/%d,Weather information is as follows:\n",
        reply.city_name,reply.year,reply.mouth,reply.day);
    if(is_multi==0)
        weather_info_view(reply.info[0],reply.tmme_interval);
    else
        for(int i=1;i<=reply.tmme_interval;i++)
            weather_info_view(reply.info[i],i);
}

void handle_reply(struct server_reply reply,uint16_t reply_index,int is_multi){
    if(reply.reply_index==reply_index){
        reply_view(reply,is_multi);
        printf("\n");
    }
    else if(reply.reply_index==NO_INFO_REPLY){
        no_info_message(reply.city_name);
        printf("\n");
    }
    else {
        printf("err! wrong reply index!");
        exit(-1);
    }
}

//control query process
int query_controller(int socketfd,char* city_name){
    int exit_flag=0;
    while(1){
        query_view();
        char choice[10];
        scanf("%s",choice);
        if(strcmp(choice,"1")==0){
            struct server_reply reply=single_query(socketfd,city_name,1);
            handle_reply(reply,SINGLE_CITY_REPLY,0);
        }else if(strcmp(choice,"2")==0){
            struct server_reply reply=multi_query(socketfd,city_name);
            handle_reply(reply,MULTI_CITY_REPLY,1);
        }else if(strcmp(choice,"3")==0){
            u_char day_num=get_day_number();
            struct server_reply reply=single_query(socketfd,city_name,day_num);
            handle_reply(reply,SINGLE_CITY_REPLY,0);
        }else if(strcmp(choice,"r")==0){//back
            cls();
            exit_flag=0;
            break;
        }else if(strcmp(choice,"c")==0){//cls
            cls();
        }else if(strcmp(choice,"#")==0){//exit
            exit_flag=1;
            break;
        }else{
            input_error_message();
        }
    }
    return exit_flag;
}

//show main information
void main_view(){
    printf("Welcome to NJUCS Weather Forecast Demo Program!\n\
Please input City Name in Chinese pinyin(e.g. nanjing or beijing)\n\
(c)cls,(#)exit\n");
}

//show city not find message
void city_not_find_message(char* city_name){
    printf("Sorry, Server does not have weather information for city %s!\n",city_name);
}

//control main process
int main_controller(int socketfd){
    while(1){
        main_view();
        char city_name[CITY_NAME_LEN];
        memset(city_name,0,sizeof(city_name));
        scanf("%s",city_name);

        if(strcmp(city_name,"c")==0){
            cls();
            continue;
        }else if(strcmp(city_name,"#")==0){
            break;
        }else{
            struct server_reply reply=city_exists_query(socketfd,city_name);
            uint16_t reply_index=reply.reply_index;

            if(reply_index==CITY_EXIST_REPLY_INDEX){
                cls();
                int exit_flag=query_controller(socketfd,reply.city_name);
                //exit
                if(exit_flag)break;
            }else if(reply_index==CITY_NOT_EXIST_REPLY_INDEX){
                city_not_find_message(reply.city_name);
            }else{
                printf("err! wrong reply index!");
                exit(-1);
            }
        }
    }
    return 1;
}

int main(){
    int socketfd=init();
    main_controller(socketfd);
    close(socketfd);
    return 0;
}